package com.hue.gulimail.gulimailorder.service.impl;

import com.alibaba.fastjson.TypeReference;
import com.alipay.api.domain.SkuInfo;
import com.baomidou.mybatisplus.core.conditions.update.Update;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.hue.common.constant.OrderConstant;
import com.hue.common.to.SeckillOrderTo;
import com.hue.common.utils.R;
import com.hue.common.vo.UserVo;
import com.hue.gulimail.gulimailorder.entity.OrderItemEntity;
import com.hue.gulimail.gulimailorder.entity.OrderReturnReasonEntity;
import com.hue.gulimail.gulimailorder.feign.CartFeignService;
import com.hue.gulimail.gulimailorder.feign.MemberFeignService;
import com.hue.gulimail.gulimailorder.feign.ProductFeignService;
import com.hue.gulimail.gulimailorder.feign.WareFeignService;
import com.hue.gulimail.gulimailorder.interceptor.LoginStatusInterceptor;
import com.hue.gulimail.gulimailorder.pay.PayVo;
import com.hue.gulimail.gulimailorder.service.OrderItemService;
import com.hue.gulimail.gulimailorder.to.OrderCreateTo;
import com.hue.gulimail.gulimailorder.vo.*;
import com.rabbitmq.client.Channel;
import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hue.common.utils.PageUtils;
import com.hue.common.utils.Query;

import com.hue.gulimail.gulimailorder.dao.OrderDao;
import com.hue.gulimail.gulimailorder.entity.OrderEntity;
import com.hue.gulimail.gulimailorder.service.OrderService;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;


/**
 * @author 姚凤桥
 */
@Service("orderService")
public class OrderServiceImpl extends ServiceImpl<OrderDao, OrderEntity> implements OrderService {
    @Autowired
    private MemberFeignService memberFeignService;
    @Autowired
    private CartFeignService cartFeignService;
    @Autowired
    private WareFeignService wareFeignService;
    @Autowired
    private OrderItemService orderItemService;
    @Autowired
    private ProductFeignService productFeignService;
    @Autowired
    private ThreadPoolExecutor threadPoolExecutor;
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<OrderEntity> page = this.page(
                new Query<OrderEntity>().getPage(params),
                new QueryWrapper<OrderEntity>()
        );

        return new PageUtils(page);
    }

    @Override
    public OrderConfirmVo getOrderConfirmVo() throws ExecutionException, InterruptedException {
        OrderConfirmVo orderConfirmVo = new OrderConfirmVo();

        Long memberId = LoginStatusInterceptor.threadLocal.get().getId();

        //获取当前请求的请求属性
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

        //1.获取用户收货地址信息
        CompletableFuture<Void> addressFuture = CompletableFuture.runAsync(() -> {
            //远程调用时将主线程的请求信息复制给异步线程
            RequestContextHolder.setRequestAttributes(requestAttributes);

            R r = memberFeignService.getByMemberId(memberId);
            List<MemberAddressVo> addressList = r.getData(new TypeReference<List<MemberAddressVo>>() {
            });

            orderConfirmVo.setMemberAddressVos(addressList);
        }, threadPoolExecutor);

        //2.获取用户勾选的商品
        CompletableFuture<Void> orderItemsFuture = CompletableFuture.runAsync(() -> {
            RequestContextHolder.setRequestAttributes(requestAttributes);

            R r = cartFeignService.getByMemberId(memberId);
            List<OrderItemVo> orderItems = r.getData(new TypeReference<List<OrderItemVo>>() {
            });

            orderConfirmVo.setOrderItemVos(orderItems);
        }, threadPoolExecutor).thenRunAsync(() -> {
            RequestContextHolder.setRequestAttributes(requestAttributes);

            List<Long> skuIds = orderConfirmVo.getOrderItemVos().stream()
                    .map(OrderItemVo::getSkuId).collect(Collectors.toList());
            //查询库存服务
            R r = wareFeignService.hasStock(skuIds);

            Map<Long, Boolean> hasStockMap = r.getData(new TypeReference<Map<Long, Boolean>>() {
            });

            for (OrderItemVo orderItemVo : orderConfirmVo.getOrderItemVos()) {
                orderItemVo.setHasStock(hasStockMap.get(orderItemVo.getSkuId()));
            }
        }, threadPoolExecutor);

        //3.设置用户积分
        orderConfirmVo.setIntegration(LoginStatusInterceptor.threadLocal.get().getIntegration());

        CompletableFuture.allOf(addressFuture, orderItemsFuture).get();

        //4.生成随机的订单令牌
        String orderToken = UUID.randomUUID().toString().replace("-", "");
        orderConfirmVo.setOrderToken(orderToken);

        //将令牌存储在redis中
        redisTemplate.opsForValue().set(
                OrderConstant.USER_ORDER_TOKEN + LoginStatusInterceptor.threadLocal.get().getId()
                , orderToken, 30, TimeUnit.MINUTES);

        return orderConfirmVo;
    }

    /**
     * 验证令牌-->创建订单-->验证价格-->锁库存...
     *
     * @param orderSubmitVo
     * @return
     */
    @Override
    // mysql默认的事务隔离级别为可重复读(REPEATABLE_READ)
    @Transactional(isolation = Isolation.REPEATABLE_READ)
    //使用Seata的全局事务(seata的分布式事务默认为AT模式，不适合大并发场景，所以在下单流程中，并不适合使用seata)
//    @GlobalTransactional
    public OrderResVo submitOrder(OrderSubmitVo orderSubmitVo) throws Exception {
        OrderResVo orderResVo = new OrderResVo();

        //1.验证令牌(从redis中获取令牌/比较令牌/删除令牌)必须保证原子操作
        String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";

        String submitToken = orderSubmitVo.getOrderToken();

        //lua脚本操作redis保证了原子性
        Long result = redisTemplate.execute(new DefaultRedisScript<>(script, Long.class),
                Arrays.asList(OrderConstant.USER_ORDER_TOKEN + LoginStatusInterceptor.threadLocal.get().getId()),
                submitToken);

        if (result == 1) {
            //令牌验证成功,生成订单
            OrderCreateTo orderCreateTo = createOrder(orderSubmitVo);
            orderResVo.setOrder(orderCreateTo.getOrder());
        } else {
            //验证失败
            orderResVo.setCode(1);
        }

        return orderResVo;
    }

    @Override
    public OrderEntity getBySn(String orderSn) {
        QueryWrapper<OrderEntity> wrapper = new QueryWrapper<>();
        wrapper.eq("order_sn", orderSn);
        return getOne(wrapper);
    }

    @Override
    public void updateStatusToCancel(OrderEntity order) {
        UpdateWrapper<OrderEntity> wrapper = new UpdateWrapper<>();
        wrapper.eq("id", order.getId()).eq("status", 0).set("status", 4);
        update(wrapper);
    }

    @Override
    public PayVo getPayVoByOrderSn(String orderSn) {
        PayVo payVo = new PayVo();

        OrderEntity order = getBySn(orderSn);
        payVo.setOut_trade_no(orderSn);
        payVo.setBody("谷粒商城订单支付");
        payVo.setSubject("谷粒商城订单支付");
        //将金额保留两位小数向上取整
        BigDecimal totalPrice = order.getPayAmount().setScale(2, BigDecimal.ROUND_UP);
        payVo.setTotal_amount(totalPrice.toString());

        return payVo;
    }

    @Override
    public List<OrderEntity> getMemberOrders() {
        UserVo userVo = LoginStatusInterceptor.threadLocal.get();
        QueryWrapper<OrderEntity> wrapper = new QueryWrapper<>();
        wrapper.eq("member_id", userVo.getId());
        wrapper.notIn("status", Arrays.asList(4, 5));

        //查询已经支付的订单信息
        List<OrderEntity> orders = list(wrapper);

        //查询每个订单的订单项
        orders = orders.stream().map(order -> {
            String orderSn = order.getOrderSn();
            List<OrderItemEntity> orderItems = orderItemService.getByOrderSn(orderSn);
            order.setOrderItems(orderItems);
            return order;
        }).collect(Collectors.toList());

        return orders;
    }

    @Override
    public void updateStatusToPay(String orderSn) {
        UpdateWrapper<OrderEntity> wrapper = new UpdateWrapper<>();
        wrapper.eq("order_sn", orderSn);
        wrapper.set("status", 3);
        update(wrapper);
    }

    @Override
    @Transactional
    public void createSeckillOrder(SeckillOrderTo seckillOrder) {
        //创建订单
        OrderEntity order = new OrderEntity();
        order.setOrderSn(seckillOrder.getOrderSn());
        order.setMemberId(seckillOrder.getMemberId());
        order.setTotalAmount(seckillOrder.getSeckillPrice().multiply(new BigDecimal(seckillOrder.getCount())));
        order.setPayAmount(seckillOrder.getSeckillPrice().multiply(new BigDecimal(seckillOrder.getCount())));
        order.setCreateTime(new Date());
        order.setStatus(0);

        save(order);

        //创建订单项
        R r = productFeignService.getBySkuId(seckillOrder.getSkuId());
        if ((Integer) r.get("code") == 0) {
            SkuInfoVo skuInfo = r.getData("skuInfo", new TypeReference<SkuInfoVo>() {
            });

            OrderItemEntity orderItem = new OrderItemEntity();
            orderItem.setOrderSn(seckillOrder.getOrderSn());
            orderItem.setSkuId(seckillOrder.getSkuId());
            orderItem.setSkuName(skuInfo.getSkuName());
            orderItem.setSkuPrice(seckillOrder.getSeckillPrice());

            orderItemService.save(orderItem);
        } else {
            throw new RuntimeException("创建订单项失败！");
        }
    }

    /**
     * 创建订单
     * 订单创建步骤：
     * 1.创建订单信息
     * 2.远程锁定库存，远程服务锁定成功后发送消息给mq，在一定时间后解锁库存
     * 3.将订单信息发送给mq，在一定时间后关闭订单
     *
     * @return
     */
    private OrderCreateTo createOrder(OrderSubmitVo orderSubmitVo) throws Exception {
        OrderCreateTo orderCreateTo = new OrderCreateTo();

        //生成订单号
        String orderSn = IdWorker.getTimeId();

        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        UserVo userVo = LoginStatusInterceptor.threadLocal.get();
        //远程查询收货地址信息
        CompletableFuture<Void> addressFuture = CompletableFuture.runAsync(() -> {
            RequestContextHolder.setRequestAttributes(requestAttributes);

            OrderEntity order = new OrderEntity();
            order.setMemberId(userVo.getId());
            order.setOrderSn(orderSn);
            order.setFreightAmount(new BigDecimal(10));
            order.setPayType(orderSubmitVo.getPayType());
            order.setStatus(0);
            order.setPayAmount(orderSubmitVo.getPayPrice());
            //远程查询收货地址信息
            R r = memberFeignService.addressInfo(orderSubmitVo.getAddressId());
            if ((Integer) r.get("code") == 0) {
                MemberAddressVo address = r.getData("memberReceiveAddress", new TypeReference<MemberAddressVo>() {
                });
                order.setReceiverName(address.getName());
                order.setReceiverPhone(address.getPhone());
                order.setReceiverPostCode(address.getPostCode());
                order.setReceiverProvince(address.getProvince());
                order.setReceiverCity(address.getCity());
                order.setReceiverRegion(address.getRegion());
                order.setNote(orderSubmitVo.getNote());
            }

            orderCreateTo.setOrder(order);
        }, threadPoolExecutor);

        //构建订单商品项
        List<OrderItemVo> orderItemVos = buildOrderItem();
        orderCreateTo.setOrderItemVos(orderItemVos);

        //设置运费
        orderCreateTo.setFare(new BigDecimal(10));

        CompletableFuture.allOf(addressFuture).get();

        //验价

        //保存订单
        save(orderCreateTo.getOrder());

        //保存订单所有购物项
        List<OrderItemEntity> orderItems = orderCreateTo.getOrderItemVos().stream().map((orderItemVo) -> {
            OrderItemEntity orderItemEntity = new OrderItemEntity();
            orderItemEntity.setOrderSn(orderSn);
            orderItemEntity.setSkuId(orderItemVo.getSkuId());
            orderItemEntity.setSkuPrice(orderItemVo.getPrice());
            orderItemEntity.setSkuName(orderItemVo.getTitle());
            return orderItemEntity;
        }).collect(Collectors.toList());
        orderItemService.saveBatch(orderItems);

        //远程锁定库存
        WareSkuLockVo wareSkuLockVo = new WareSkuLockVo();
        wareSkuLockVo.setOrderSn(orderSn);
        wareSkuLockVo.setLocks(orderCreateTo.getOrderItemVos());
        R r = wareFeignService.lockOrder(wareSkuLockVo);
        if ((Integer) r.get("code") != 0) {
            throw new RuntimeException("没有库存了!");
        }

        //模拟调用其他服务出现异常，如果不使用分布式事务那么库存服务将不能回滚
//        int i = 10 / 0;

        //订单创建成功之后通知mq，提醒在一定时间后关闭订单
        rabbitTemplate.convertAndSend("order-event-exchange", "order.create.queue", orderCreateTo.getOrder());

        return orderCreateTo;
    }

    private List<OrderItemVo> buildOrderItem() {
        //获取用户购物车中选中的所有项
        R r = cartFeignService.getByMemberId(LoginStatusInterceptor.threadLocal.get().getId());
        if ((Integer) r.get("code") == 0) {
            List<OrderItemVo> orderItemVos = r.getData(new TypeReference<List<OrderItemVo>>() {
            });
            return orderItemVos;
        }
        return null;
    }

    /**
     * 测试接受RabbitMq的消息
     * 参数类型可以写以下类型：
     * 1.Message message: RabbitMq原生消息类型，包含了消息的头+体
     * 2.T t: 消息的真实类型
     * 3.Channel channel: 当前传输消息的通道
     * <p>
     * Queue:
     * 1.一个队列可以被多个客户端监听，但是只能有一个客户端接受到消息，接受到后这个消息会删除
     * 2.只有当前方法运行完毕后才能接受下一个消息
     */
//    @RabbitListener(queues = {"queue.java"})
    public void getMsg(Message message, OrderReturnReasonEntity order, Channel channel) {
        System.out.println(order);

        //获取消息的标签
        long deliveryTag = message.getMessageProperties().getDeliveryTag();

        try {
            //手动签收消息(非批量)
            channel.basicAck(deliveryTag, false);

            //手动拒收消息(非批量，并且拒收的消息不会被重新入队)
//            channel.basicNack(deliveryTag, false, false);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    /**
     * 测试本地事务
     */
    @Transactional(timeout = 30)
    public void a() {
        //如果以当前对象来调用本类的其他事务方法，其他的事务方法的设置都不会生效(本类调用绕过了代理对象)
        b();
        c();

        //使用AspectJ生成了本类的代理对象
        OrderServiceImpl currentProxy = (OrderServiceImpl) AopContext.currentProxy();
        //使用代理对象来调用本类的方法，其他事务方法的事务设置才会生效
        currentProxy.b();
        currentProxy.c();
    }

    /**
     * Propagation.REQUIRED:继承a的事务,即使自己设置了timeout也没用
     */
    @Transactional(propagation = Propagation.REQUIRED, timeout = 20)
    public void b() {

    }

    /**
     * Propagation.REQUIRES_NEW：开启了新事务，设置的timeout有效
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW, timeout = 20)
    public void c() {

    }
}
